﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.IO;

namespace BZ.Common
{
    public class RegexUtility
    {
        public struct RegexParam
        {
            /// <summary>
            /// 源串
            /// </summary>
            public string Source;

            /// <summary>
            /// 正则串
            /// </summary>
            public string RegStr;

            /// <summary>
            /// 超时时间
            /// </summary>
            public int TimeOutMs;

            /// <summary>
            /// 正则设置
            /// </summary>
            public RegexOptions Options;


            /// <summary>
            /// 初始化参数
            /// </summary>
            /// <param name="source">来源</param>
            /// <param name="regstr">正则规则</param>
            /// <param name="timeoutms">超时设置，缺省：200ms</param>
            /// <param name="options">正则判断设置，缺省：IgnoreCase </param>
            public RegexParam(string source, string regstr, RegexOptions options = RegexOptions.None, int timeoutms = 200)
            {
                Source = source;
                RegStr = regstr;
                TimeOutMs = timeoutms;
                Options = options;
            }

        }

        /// <summary>
        /// Html注释内容匹配规则
        /// </summary>
        public const string RegRule_HtmlCommentedOut = @"<!--[\s\S]*?-->";


        /// <summary>
        /// 正则匹配器
        /// 可以处理多个匹配
        /// </summary>
        /// <param name="rparam">参数</param>
        /// <param name="isTimeOut">是否超超时</param>
        /// <param name="splitStr">分隔字符串</param>
        /// <returns>如果有匹配值则返回，没有则返回空字符串</returns>
        public static string RegexMatcher(RegexParam rparam, out bool isTimeOut, string splitStr)
        {
            List<string> matchList = RegexMatcher(rparam, out isTimeOut);
            return StringUtility.GetStringByList(matchList, splitStr ?? "");
        }

        /// <summary>
        /// 正则匹配器
        /// 可以处理多个匹配
        /// </summary>
        /// <param name="_source">源</param>
        /// <param name="_regexStr">正则串</param>
        /// <param name="_regexOption">匹配选项，默认忽略大小写</param>
        /// <param name="_splitStr">分隔内容</param>
        /// <returns>如果有匹配值则返回，没有则返回空字符串</returns>
        public static List<string> RegexMatcher(RegexParam rparam, out bool isTimeOut)
        {
            bool timeOut = true;
            List<string> _result = new List<string>();
            if (!string.IsNullOrEmpty(rparam.Source) && !string.IsNullOrEmpty(rparam.RegStr))
            {
                CancellationTokenSource cts = new CancellationTokenSource();
                Thread thread = new Thread(() =>
                {
                    MatchCollection myMatchs = null;
                    myMatchs = Regex.Matches(rparam.Source, rparam.RegStr, rparam.Options);
                    if (myMatchs != null && myMatchs.Count > 0)
                    {
                        foreach (Match myItem in myMatchs)
                        {
                            _result.Add(myItem.Value);
                        }
                    }
                    timeOut = false;
                });
                cts.Token.Register(() =>
                {
                    thread.Abort();
                });
                thread.Start();
                //双线程并行，调用线程休眠
                Thread.Sleep(rparam.TimeOutMs);
                cts.Cancel();

                thread.Join();//合并线程
            }
            if (timeOut)
            {
                _result.Clear();
            }
            isTimeOut = timeOut;
            return _result;
        }


        /// <summary>
        /// 是否含有中文字符
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsChinese(string source)
        {
            bool myResult = false;
            if (!string.IsNullOrEmpty(source))
            {
                myResult = Regex.IsMatch(source, @"[\u4e00-\u9fa5]");
            }
            return myResult;
        }


        #region html 匹配

        public const string GetMetaEncodeRegexStr = @"<meta[^>]*\bcharset\s*=\s*[""']?(?<charset>[-a-zA-Z_0-9]+)\b[""']?[^>]*>";

        /// <summary>
        /// 解析页面编码类型
        /// </summary>
        /// <param name="htmlStream">Html ResponseStream</param>
        /// <param name="pageCode">out:页面编码</param>
        /// <returns></returns>
        public static ResultInfo GetHtmlEncode(Stream htmlStream, out Encoding pageCode)
        {
            ResultInfo result = new ResultInfo();
            pageCode = Encoding.UTF8;

            try
            {
                List<byte> lst = new List<byte>();
                int nRead = 0;
                while ((nRead = htmlStream.ReadByte()) != -1) lst.Add((byte)nRead);
                byte[] byHtml = lst.ToArray();
                string htmlStr = Encoding.UTF8.GetString(byHtml, 0, byHtml.Length);
                result = GetHtmlEncode(htmlStr, out pageCode);
            }
            catch (Exception ex)
            {
                result.IsSuccess = false;
                result.Message = ex.Message;
            }
            return result;
        }

        /// <summary>
        /// 解析页面编码类型
        /// </summary>
        /// <param name="html">页面串，utf8解析后的串</param>
        /// <param name="pageCode">out:页面编码</param>
        /// <returns></returns>
        public static ResultInfo GetHtmlEncode(string html, out Encoding pageCode)
        {
            ResultInfo result = new ResultInfo();
            pageCode = Encoding.UTF8;

            if (string.IsNullOrEmpty(html))
            {
                result.IsSuccess = false;
                result.Message = "输入的html串为空。";
                return result;
            }

            try
            {
                string charset = Regex.Match(html, GetMetaEncodeRegexStr, RegexOptions.IgnoreCase | RegexOptions.Singleline).Groups["charset"].Value;
                pageCode = Encoding.GetEncoding(charset);
                result.IsSuccess = true;
            }
            catch (Exception ex)
            {
                result.IsSuccess = false;
                result.Message = ex.Message;
            }

            return result;
        }

        /// <summary>
        /// 获取匹配的信息列表
        /// </summary>
        /// <param name="source">来源</param>
        /// <param name="isTimeOut">是否超时</param>
        /// <param name="tagName">html标签，例:a</param>
        /// <param name="attName">标签内属性，例：class</param>
        /// <param name="attValue">属性内包含的某一值,例:result</param>
        /// <param name="timeoutms">超时设置，ms</param>
        /// <param name="options">正则规则</param>
        /// <returns></returns>
        public static List<string> GetPairTagInnerHtmls(string source, out bool isTimeOut, string tagName, string attName = "", string attValue = "", int timeoutms = 200, RegexOptions options = RegexOptions.None)
        {
            string regStr = GetPairTagInnerRegexStr(tagName, attName, attValue);
            RegexParam rparam = new RegexParam(source, regStr, options | RegexOptions.Singleline, timeoutms);
            return RegexMatcher(rparam, out isTimeOut);
        }

        /// <summary>
        /// 获取嵌套的 指定特征标签及其内部内容的 正则规则
        /// </summary>
        /// <param name="tagName">html标签，例:a</param>
        /// <param name="attName">标签内属性，例：class</param>
        /// <param name="attValue">属性内包含的某一值,例1单一值:class1；例2值位于后面：[^""]*\class2；例3包含值：[^""]*class2[^""]*</param>
        /// <returns></returns>
        public static string GetPairTagInnerRegexStr(string tagName, string attName = "", string attValue = "")
        {
            if (!string.IsNullOrEmpty(tagName))
            {
                string attSubRegstr = string.Empty;

                //拼装属性及属性中所包含的值部分。
                if (!string.IsNullOrEmpty(attName))
                {
                    string attValueSubRegstr = string.Empty;
                    if(!string.IsNullOrEmpty(attValue))
                    {
                        attValueSubRegstr = string.Format(@"\s*=\s*(?<Quote>[""']?)(\s*{0}\s*)[^""']*(?(Quote)\k<Quote>)[""']?", attValue);
                    }

                    attSubRegstr = string.Format(@"\s({0}){1}[^>]*", attName, attValueSubRegstr);
                }

                return string.Format(@"<(?<HtmlTag>{0})[^>]*{1}>((?<Nested><\k<HtmlTag>[^>]*>)|</\k<HtmlTag>>(?<-Nested>)|.*?)*</\k<HtmlTag>>", tagName, attSubRegstr);
            }
            else
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// 获取所有尖括号内的标签内容规则
        /// </summary>
        public const string GetTagRegexStr = @"<[^>]*>";

        /// <summary>
        /// 从标签中提取url
        /// 优先判断是否为绝对路径，然后在尝试判断是否是相对路径
        /// </summary>
        /// <param name="tagHtml">taghtml</param>
        /// <param name="baseUrl">基础地址用于进行相对路径地址处理</param>
        /// <returns>
        /// 绝对路径格式：http://www.baidu.com/xxxxxxxxx
        /// 相对路径格式：/xxxxxxxxx/aaaaaaaaaa/bbbbbb 如果包含baseurl设置则返回 http://www.baidu.com/xxxxxxxxx 地址样式
        /// </returns>
        public static string GetUrlFromTag(string tagHtml, string baseUrl = null)
        {
            string rslt = string.Empty;
            if (!string.IsNullOrEmpty(tagHtml))
            {
                string urlStrRule = @"href\s*=\s*""[^""]*""";
                Match urlStrMatch = Regex.Match(tagHtml, urlStrRule, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                if (urlStrMatch != null && !string.IsNullOrEmpty(urlStrMatch.Value))
                {
                    string temp = Regex.Replace(urlStrMatch.Value, @"href\s*=\s*""", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);//去掉开头的href="部分
                    //依序检查
                    //在排除baseRelUrl 可能性后 才能进行pageRelUrl
                    Match absUrl = Regex.Match(temp, @"^[\w]{4,5}://[^""]*", RegexOptions.IgnoreCase | RegexOptions.Singleline); //绝对路径匹配    
                    Match baseRelUrl = Regex.Match(temp, @"^/[^""]*", RegexOptions.IgnoreCase | RegexOptions.Singleline); //基于域名的相对路径匹配
                    Match pageRelUrl = Regex.Match(temp, @"[^""]*", RegexOptions.IgnoreCase | RegexOptions.Singleline); //基于当前页的相对路径匹配

                    if (absUrl != null && !string.IsNullOrEmpty(absUrl.Value))
                    {
                        rslt = absUrl.Value;
                    }
                    else if (baseRelUrl != null && !string.IsNullOrEmpty(baseRelUrl.Value))
                    {
                        string baseName = string.Empty;
                        if (!string.IsNullOrEmpty(baseUrl))
                        {
                            string domainStrRule = @"^[\w]{4,5}://[^/]*";
                            Match domainStrMatch = Regex.Match(baseUrl, domainStrRule, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                            if (domainStrMatch != null && !string.IsNullOrEmpty(domainStrMatch.Value))
                            {
                                baseName = domainStrMatch.Value;
                            }
                        }
                        rslt = baseName + baseRelUrl.Value;
                    }
                    else if (pageRelUrl != null && !string.IsNullOrEmpty(pageRelUrl.Value))
                    {
                        string baseName = string.Empty;
                        if (!string.IsNullOrEmpty(baseUrl))
                        {
                            baseName = baseUrl.Substring(0, baseUrl.LastIndexOf("/") + 1);
                        }
                        rslt = baseName + pageRelUrl.Value;
                    }
                }
            }
            return rslt;
        }

        /// <summary>
        /// 从tag中读取某属性的全部值
        /// </summary>
        /// <param name="tagHtml">taghtml</param>
        /// <param name="attiName">属性名</param>
        /// <returns>
        /// 属性值
         /// </returns>
        public static string GetAttiValueFromTag(string tagHtml, string attiName)
        {
            string rslt = string.Empty;
            if (!string.IsNullOrEmpty(tagHtml) && !string.IsNullOrEmpty(attiName))
            {
                string strRule1 = attiName + @"\s*=\s*""[^""]*""";//有双引号的属性
                string strRule2 = attiName + @"\s*=\s*'[^']*'";//有单引号的属性
                string strRule3 = attiName + @"\s*=\s*[^\s]*";//无双引号的属性

                Match strMatch1 = Regex.Match(tagHtml, strRule1, RegexOptions.IgnoreCase | RegexOptions.Singleline);

                if (strMatch1 != null && !string.IsNullOrEmpty(strMatch1.Value))
                {
                    string temp = Regex.Replace(strMatch1.Value, attiName + @"\s*=\s*""", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);//去掉开头的name="部分
                    rslt = temp.Substring(0, temp.Length - 1);//去掉最后一个引号
                }
                else
                {
                    Match strMatch2 = Regex.Match(tagHtml, strRule2, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                    if (strMatch2 != null && !string.IsNullOrEmpty(strMatch2.Value))
                    {
                        string temp = Regex.Replace(strMatch2.Value, attiName + @"\s*=\s*'", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);//去掉开头的name="部分
                        rslt = temp.Substring(0, temp.Length - 1);//去掉最后一个引号
                    }
                    else
                    {
                        Match strMatch3 = Regex.Match(tagHtml, strRule3, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                        if (strMatch3 != null && !string.IsNullOrEmpty(strMatch3.Value))
                        {
                            int index = strMatch3.Value.IndexOf('=');
                            rslt = strMatch3.Value.Substring(index + 1);
                        }
                    }
                }
                    
            }
            return rslt;
        }


        /// <summary>
        /// 根据Url获取域名 例:baidu.com.cn
        /// </summary>
        /// <param name="url">网址</param>
        /// <returns></returns>
        public static string GetDomainNameByUrl(string url)
        {
            StringBuilder rslt = new StringBuilder();
            if (!string.IsNullOrEmpty(url))
            {
                string domainStrRule = @"(?<=://)*[a-zA-Z\.0-9\-]+(?=\/)";
                Match domainStrMatch = Regex.Match(url, domainStrRule, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                if (domainStrMatch != null && !string.IsNullOrEmpty(domainStrMatch.Value))
                {
                    rslt.Append(domainStrMatch.Value);//设置初值
                    //开始分解
                    string[] domainNameTmp = domainStrMatch.Value.Split('.');
                    if (domainNameTmp.Length > 2)
                    {
                        //域名结构大于2个
                        rslt.Clear();
                        for (int i = 1; i < domainNameTmp.Length; i++)
                        {
                            rslt.Append(domainNameTmp[i] + ".");
                        }
                        rslt.Remove(rslt.Length - 1, 1);
                    }
                }
            }
            return rslt.ToString();
        }

        #endregion 
    }
}
